View Javadoc
1 package org.apache.commons.betwixt; 2 3 /* 4 * ==================================================================== 5 * 6 * The Apache Software License, Version 1.1 7 * 8 * Copyright (c) 1999-2002 The Apache Software Foundation. All rights 9 * reserved. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 15 * 1. Redistributions of source code must retain the above copyright 16 * notice, this list of conditions and the following disclaimer. 17 * 18 * 2. Redistributions in binary form must reproduce the above copyright 19 * notice, this list of conditions and the following disclaimer in 20 * the documentation and/or other materials provided with the 21 * distribution. 22 * 23 * 3. The end-user documentation included with the redistribution, if 24 * any, must include the following acknowlegement: 25 * "This product includes software developed by the 26 * Apache Software Foundation (http://www.apache.org/)." 27 * Alternately, this acknowlegement may appear in the software itself, 28 * if and wherever such third-party acknowlegements normally appear. 29 * 30 * 4. The names "The Jakarta Project", "Commons", and "Apache Software 31 * Foundation" must not be used to endorse or promote products derived 32 * from this software without prior written permission. For written 33 * permission, please contact apache@apache.org. 34 * 35 * 5. Products derived from this software may not be called "Apache" 36 * nor may "Apache" appear in their names without prior written 37 * permission of the Apache Group. 38 * 39 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 40 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 41 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 42 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR 43 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 44 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 45 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 46 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 47 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 48 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 49 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 50 * SUCH DAMAGE. 51 * ==================================================================== 52 * 53 * This software consists of voluntary contributions made by many 54 * individuals on behalf of the Apache Software Foundation. For more 55 * information on the Apache Software Foundation, please see 56 * <http://www.apache.org/>;. 57 */ 58 59 import java.beans.BeanDescriptor; 60 import java.beans.BeanInfo; 61 import java.beans.Introspector; 62 import java.beans.IntrospectionException; 63 import java.beans.PropertyDescriptor; 64 import java.lang.reflect.Method; 65 import java.net.URL; 66 import java.util.ArrayList; 67 import java.util.List; 68 import java.util.Map; 69 import java.util.HashMap; 70 71 import org.apache.commons.logging.LogFactory; 72 import org.apache.commons.logging.Log; 73 74 import org.apache.commons.betwixt.expression.EmptyExpression; 75 import org.apache.commons.betwixt.expression.IteratorExpression; 76 import org.apache.commons.betwixt.expression.MethodExpression; 77 import org.apache.commons.betwixt.expression.MethodUpdater; 78 import org.apache.commons.betwixt.expression.StringExpression; 79 import org.apache.commons.betwixt.digester.XMLBeanInfoDigester; 80 import org.apache.commons.betwixt.digester.XMLIntrospectorHelper; 81 import org.apache.commons.betwixt.strategy.DefaultNameMapper; 82 import org.apache.commons.betwixt.strategy.DefaultPluralStemmer; 83 import org.apache.commons.betwixt.strategy.NameMapper; 84 import org.apache.commons.betwixt.strategy.PluralStemmer; 85 86 /*** 87 * <p><code>XMLIntrospector</code> an introspector of beans to create a 88 * XMLBeanInfo instance.</p> 89 * 90 * <p>By default, <code>XMLBeanInfo</code> caching is switched on. 91 * This means that the first time that a request is made for a <code>XMLBeanInfo</code> 92 * for a particular class, the <code>XMLBeanInfo</code> is cached. 93 * Later requests for the same class will return the cached value.</p> 94 * 95 * <p>Note :</p> 96 * <p>This class makes use of the <code>java.bean.Introspector</code> 97 * class, which contains a BeanInfoSearchPath. To make sure betwixt can 98 * do his work correctly, this searchpath is completely ignored during 99 * processing. The original values will be restored after processing finished 100 * </p> 101 * 102 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a> 103 * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a> 104 * @version $Id: XMLIntrospector.java,v 1.10 2002/10/26 14:30:54 mvdb Exp $ 105 */ 106 public class XMLIntrospector { 107 108 /*** Log used for logging (Doh!) */ 109 protected Log log = LogFactory.getLog( XMLIntrospector.class ); 110 111 /*** should attributes or elements be used for primitive types */ 112 private boolean attributesForPrimitives = false; 113 114 /*** should we wrap collections in an extra element? */ 115 private boolean wrapCollectionsInElement = true; 116 117 /*** Is <code>XMLBeanInfo</code> caching enabled? */ 118 boolean cachingEnabled = true; 119 120 /*** Maps classes to <code>XMLBeanInfo</code>'s */ 121 protected Map cacheXMLBeanInfos = new HashMap(); 122 123 /*** Digester used to parse the XML descriptor files */ 124 private XMLBeanInfoDigester digester; 125 126 // pluggable strategies 127 128 /*** The strategy used to detect matching singular and plural properties */ 129 private PluralStemmer pluralStemmer; 130 131 /*** The strategy used to convert bean type names into element names */ 132 private NameMapper elementNameMapper; 133 134 /*** 135 * The strategy used to convert bean type names into attribute names 136 * It will default to the normal nameMapper. 137 */ 138 private NameMapper attributeNameMapper; 139 140 private boolean useBeanInfoSearchPath = false; 141 142 /*** Base constructor */ 143 public XMLIntrospector() { 144 } 145 146 /*** 147 * <p> Get the current logging implementation. </p> 148 */ 149 public Log getLog() { 150 return log; 151 } 152 153 /*** 154 * <p> Set the current logging implementation. </p> 155 */ 156 public void setLog(Log log) { 157 this.log = log; 158 } 159 160 /*** 161 * Is <code>XMLBeanInfo</code> caching enabled? 162 */ 163 public boolean isCachingEnabled() { 164 return cachingEnabled; 165 } 166 167 /*** 168 * Set whether <code>XMLBeanInfo</code> caching should be enabled. 169 */ 170 public void setCachingEnabled(boolean cachingEnabled) { 171 this.cachingEnabled = cachingEnabled; 172 } 173 174 /*** 175 * Flush existing cached <code>XMLBeanInfo</code>'s. 176 */ 177 public void flushCache() { 178 cacheXMLBeanInfos.clear(); 179 } 180 181 /*** Create a standard <code>XMLBeanInfo</code> by introspection 182 The actual introspection depends only on the <code>BeanInfo</code> 183 associated with the bean. 184 */ 185 public XMLBeanInfo introspect(Object bean) throws IntrospectionException { 186 if (log.isDebugEnabled()) { 187 log.debug( "Introspecting..." ); 188 log.debug(bean); 189 } 190 return introspect( bean.getClass() ); 191 } 192 193 /*** Create a standard <code>XMLBeanInfo</code> by introspection. 194 The actual introspection depends only on the <code>BeanInfo</code> 195 associated with the bean. 196 */ 197 public XMLBeanInfo introspect(Class aClass) throws IntrospectionException { 198 // we first reset the beaninfo searchpath. 199 String[] searchPath = null; 200 if (!useBeanInfoSearchPath) 201 { 202 searchPath = Introspector.getBeanInfoSearchPath(); 203 Introspector.setBeanInfoSearchPath(new String[] { }); 204 } 205 206 XMLBeanInfo xmlInfo = null; 207 if ( cachingEnabled ) { 208 // if caching is enabled, try in caching first 209 xmlInfo = (XMLBeanInfo) cacheXMLBeanInfos.get( aClass ); 210 } 211 if (xmlInfo == null) { 212 // lets see if we can find an XML descriptor first 213 if ( log.isDebugEnabled() ) { 214 log.debug( "Attempting to lookup an XML descriptor for class: " + aClass ); 215 } 216 217 xmlInfo = findByXMLDescriptor( aClass ); 218 if ( xmlInfo == null ) { 219 BeanInfo info = Introspector.getBeanInfo( aClass ); 220 xmlInfo = introspect( info ); 221 } 222 223 if (xmlInfo != null) { 224 cacheXMLBeanInfos.put( aClass, xmlInfo ); 225 } 226 } else { 227 log.trace("Used cached XMLBeanInfo."); 228 } 229 230 if (log.isTraceEnabled()) { 231 log.trace(xmlInfo); 232 } 233 if (!useBeanInfoSearchPath) 234 { 235 // we restore the beaninfo searchpath. 236 Introspector.setBeanInfoSearchPath(searchPath); 237 } 238 239 return xmlInfo; 240 } 241 242 /*** Create a standard <code>XMLBeanInfo</code> by introspection. 243 The actual introspection depends only on the <code>BeanInfo</code> 244 associated with the bean. 245 */ 246 public XMLBeanInfo introspect(BeanInfo beanInfo) throws IntrospectionException { 247 XMLBeanInfo answer = createXMLBeanInfo( beanInfo ); 248 249 BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor(); 250 Class beanClass = beanDescriptor.getBeanClass(); 251 252 ElementDescriptor elementDescriptor = new ElementDescriptor(); 253 elementDescriptor.setLocalName( getElementNameMapper().mapTypeToElementName( beanDescriptor.getName() ) ); 254 elementDescriptor.setPropertyType( beanInfo.getBeanDescriptor().getBeanClass() ); 255 256 if (log.isTraceEnabled()) { 257 log.trace(elementDescriptor); 258 } 259 260 // add default string value for primitive types 261 if ( isPrimitiveType( beanClass ) ) { 262 elementDescriptor.setTextExpression( StringExpression.getInstance() ); 263 elementDescriptor.setPrimitiveType(true); 264 } 265 else if ( isLoopType( beanClass ) ) { 266 ElementDescriptor loopDescriptor = new ElementDescriptor(); 267 loopDescriptor.setContextExpression( 268 new IteratorExpression( EmptyExpression.getInstance() ) 269 ); 270 if ( Map.class.isAssignableFrom( beanClass ) ) { 271 loopDescriptor.setQualifiedName( "entry" ); 272 } 273 elementDescriptor.setElementDescriptors( new ElementDescriptor[] { loopDescriptor } ); 274 275 /* 276 elementDescriptor.setContextExpression( 277 new IteratorExpression( EmptyExpression.getInstance() ) 278 ); 279 */ 280 } 281 else { 282 List elements = new ArrayList(); 283 List attributes = new ArrayList(); 284 285 addProperties( beanInfo, elements, attributes ); 286 287 BeanInfo[] additionals = beanInfo.getAdditionalBeanInfo(); 288 if ( additionals != null ) { 289 for ( int i = 0, size = additionals.length; i < size; i++ ) { 290 BeanInfo otherInfo = additionals[i]; 291 addProperties( otherInfo, elements, attributes ); 292 } 293 } 294 295 int size = elements.size(); 296 if ( size > 0 ) { 297 ElementDescriptor[] descriptors = new ElementDescriptor[size]; 298 elements.toArray( descriptors ); 299 elementDescriptor.setElementDescriptors( descriptors ); 300 } 301 size = attributes.size(); 302 if ( size > 0 ) { 303 AttributeDescriptor[] descriptors = new AttributeDescriptor[size]; 304 attributes.toArray( descriptors ); 305 elementDescriptor.setAttributeDescriptors( descriptors ); 306 } 307 } 308 309 answer.setElementDescriptor( elementDescriptor ); 310 311 // default any addProperty() methods 312 XMLIntrospectorHelper.defaultAddMethods( this, elementDescriptor, beanClass ); 313 314 return answer; 315 } 316 317 318 // Properties 319 //------------------------------------------------------------------------- 320 321 /*** Should attributes (or elements) be used for primitive types. 322 */ 323 public boolean isAttributesForPrimitives() { 324 return attributesForPrimitives; 325 } 326 327 /*** Set whether attributes (or elements) should be used for primitive types. */ 328 public void setAttributesForPrimitives(boolean attributesForPrimitives) { 329 this.attributesForPrimitives = attributesForPrimitives; 330 } 331 332 /*** @return whether we should we wrap collections in an extra element? */ 333 public boolean isWrapCollectionsInElement() { 334 return wrapCollectionsInElement; 335 } 336 337 /*** Sets whether we should we wrap collections in an extra element? */ 338 public void setWrapCollectionsInElement(boolean wrapCollectionsInElement) { 339 this.wrapCollectionsInElement = wrapCollectionsInElement; 340 } 341 342 /*** 343 * @return the strategy used to detect matching singular and plural properties 344 */ 345 public PluralStemmer getPluralStemmer() { 346 if ( pluralStemmer == null ) { 347 pluralStemmer = createPluralStemmer(); 348 } 349 return pluralStemmer; 350 } 351 352 /*** 353 * Sets the strategy used to detect matching singular and plural properties 354 */ 355 public void setPluralStemmer(PluralStemmer pluralStemmer) { 356 this.pluralStemmer = pluralStemmer; 357 } 358 359 /*** 360 * @return the strategy used to convert bean type names into element names 361 * @deprecated getNameMapper is split up in {@link #getElementNameMapper()} and {@link #getAttributeNameMapper()} 362 */ 363 public NameMapper getNameMapper() { 364 return getElementNameMapper(); 365 } 366 367 /*** 368 * Sets the strategy used to convert bean type names into element names 369 * @param nameMapper 370 * @deprecated setNameMapper is split up in {@link #setElementNameMapper(NameMapper)} and {@link #setAttributeNameMapper(NameMapper)} 371 */ 372 public void setNameMapper(NameMapper nameMapper) { 373 setElementNameMapper(nameMapper); 374 } 375 376 377 /*** 378 * @return the strategy used to convert bean type names into element 379 * names. If no element mapper is currently defined then a default one is created. 380 */ 381 public NameMapper getElementNameMapper() { 382 if ( elementNameMapper == null ) { 383 elementNameMapper = createNameMapper(); 384 } 385 return elementNameMapper; 386 } 387 388 /*** 389 * Sets the strategy used to convert bean type names into element names 390 * @param nameMapper 391 */ 392 public void setElementNameMapper(NameMapper nameMapper) { 393 this.elementNameMapper = nameMapper; 394 } 395 396 397 /*** 398 * @return the strategy used to convert bean type names into attribute 399 * names. If no attributeNamemapper is known, it will default to the ElementNameMapper 400 */ 401 public NameMapper getAttributeNameMapper() { 402 if (attributeNameMapper == null) { 403 attributeNameMapper = createNameMapper(); 404 } 405 return attributeNameMapper; 406 } 407 408 409 /*** 410 * Sets the strategy used to convert bean type names into attribute names 411 * @param nameMapper 412 */ 413 public void setAttributeNameMapper(NameMapper nameMapper) { 414 this.attributeNameMapper = nameMapper; 415 } 416 417 418 419 420 421 422 // Implementation methods 423 //------------------------------------------------------------------------- 424 425 /*** 426 * A Factory method to lazily create a new strategy to detect matching singular and plural properties 427 */ 428 protected PluralStemmer createPluralStemmer() { 429 return new DefaultPluralStemmer(); 430 } 431 432 /*** 433 * A Factory method to lazily create a strategy used to convert bean type names into element names 434 */ 435 protected NameMapper createNameMapper() { 436 return new DefaultNameMapper(); 437 } 438 439 /*** 440 * Attempt to lookup the XML descriptor for the given class using the 441 * classname + ".betwixt" using the same ClassLoader used to load the class 442 * or return null if it could not be loaded 443 */ 444 protected synchronized XMLBeanInfo findByXMLDescriptor( Class aClass ) { 445 // trim the package name 446 String name = aClass.getName(); 447 int idx = name.lastIndexOf( '.' ); 448 if ( idx >= 0 ) { 449 name = name.substring( idx + 1 ); 450 } 451 name += ".betwixt"; 452 453 URL url = aClass.getResource( name ); 454 if ( url != null ) { 455 try { 456 String urlText = url.toString(); 457 if ( log.isDebugEnabled( )) { 458 log.debug( "Parsing Betwixt XML descriptor: " + urlText ); 459 } 460 // synchronized method so this digester is only used by 461 // one thread at once 462 if ( digester == null ) { 463 digester = new XMLBeanInfoDigester(); 464 digester.setXMLIntrospector( this ); 465 } 466 digester.setBeanClass( aClass ); 467 return (XMLBeanInfo) digester.parse( urlText ); 468 } 469 catch (Exception e) { 470 log.warn( "Caught exception trying to parse: " + name, e ); 471 } 472 } 473 return null; 474 } 475 476 /*** Loop through properties and process each one */ 477 protected void addProperties( 478 BeanInfo beanInfo, 479 List elements, 480 List attributes) 481 throws 482 IntrospectionException { 483 PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors(); 484 if ( descriptors != null ) { 485 for ( int i = 0, size = descriptors.length; i < size; i++ ) { 486 addProperty(beanInfo, descriptors[i], elements, attributes); 487 } 488 } 489 if (log.isTraceEnabled()) { 490 log.trace(elements); 491 log.trace(attributes); 492 } 493 } 494 495 /*** 496 * Process a property. 497 * Go through and work out whether it's a loop property, a primitive or a standard. 498 * The class property is ignored. 499 */ 500 protected void addProperty( 501 BeanInfo beanInfo, 502 PropertyDescriptor propertyDescriptor, 503 List elements, 504 List attributes) 505 throws 506 IntrospectionException { 507 NodeDescriptor nodeDescriptor = XMLIntrospectorHelper 508 .createDescriptor(propertyDescriptor, 509 isAttributesForPrimitives(), 510 this); 511 if (nodeDescriptor == null) { 512 return; 513 } 514 if (nodeDescriptor instanceof ElementDescriptor) { 515 elements.add(nodeDescriptor); 516 } else { 517 attributes.add(nodeDescriptor); 518 } 519 } 520 521 /*** Factory method to create XMLBeanInfo instances */ 522 protected XMLBeanInfo createXMLBeanInfo( BeanInfo beanInfo ) { 523 XMLBeanInfo answer = new XMLBeanInfo( beanInfo.getBeanDescriptor().getBeanClass() ); 524 return answer; 525 } 526 527 /*** Returns true if the type is a loop type */ 528 public boolean isLoopType(Class type) { 529 return XMLIntrospectorHelper.isLoopType(type); 530 } 531 532 533 /*** Returns true for primitive types */ 534 public boolean isPrimitiveType(Class type) { 535 return XMLIntrospectorHelper.isPrimitiveType(type); 536 } 537 /*** 538 * By default it will be false. 539 * 540 * @return boolean if the beanInfoSearchPath should be used. 541 */ 542 public boolean useBeanInfoSearchPath() { 543 return useBeanInfoSearchPath; 544 } 545 546 /*** 547 * Specifies if you want to use the beanInfoSearchPath 548 * @see java.beans.Introspector for more details 549 * @param useBeanInfoSearchPath 550 */ 551 public void setUseBeanInfoSearchPath(boolean useBeanInfoSearchPath) { 552 this.useBeanInfoSearchPath = useBeanInfoSearchPath; 553 } 554 555 }

This page was automatically generated by Maven